using System;
using System.Collections.Generic;
using System.Collections;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// <see cref="TwoTypesIndexBasedList{T}"/> based list supporting hierarchy of <typeparamref name="T"/> items through <see cref="IHierarchicalSubsetList{T}"/>.
	/// </summary>
	/// <remarks>
	/// <typeparam name="TSubsetList"><see cref="ISubsetListBase{T}"/> implementation type.</typeparam>
	/// <typeparam name="T">Main item type.</typeparam>
	public class HierarchicalIndexBasedSubsetList<TSubsetList, T>
		: TwoTypesIndexBasedList<TSubsetList, T>,
		IHierarchicalSubsetList<T>
		where TSubsetList: ISubsetListBase<T>
	{
		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="list1">List with <see cref="ISubsetListBase{T}"/> items - see <see cref="List1"/>.</param>
		/// <param name="list2">List with <typeparamref name="T"/> items - see <see cref="List2"/>.</param>
		/// <exception cref="ArgumentException"><paramref name="list2"/> has a null item.</exception>
		public HierarchicalIndexBasedSubsetList(IList<TSubsetList> list1, IList<T> list2)
			: base(
					new ItemsChangeList<TSubsetList>(
						list1, delegate(TSubsetList item) { return item != null; }),
						list2)
		{
		}
		/// <summary>
		/// Constructor.
		/// <see cref="List1"/> is empty.
		/// </summary>
		/// <param name="list2">List with <typeparamref name="T"/> items - see <see cref="List2"/>.</param>
		public HierarchicalIndexBasedSubsetList(IList<T> list2)
			: this(null, list2)
		{
		}

		#endregion

		/// <summary>
		/// Copies specified items to <paramref name="array"/>.
		/// </summary>
		/// <param name="index">Starting index of this list.</param>
		/// <param name="array">Target array.</param>
		/// <param name="arrayIndex">Starting index of target array from which items will be assigned.</param>
		/// <param name="count">Number of items to copy from this list.</param>
		/// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
		/// <exception cref="ArgumentException"><paramref name="array"/> does not have enough space or is multidimensional.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="index"/> is &lt; 0 or &gt;= <see cref="Count"/>.
		/// </exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="count"/> is &lt;= 0 or is bigger then available items from <paramref name="index"/> to the end of the list.
		/// </exception>
		/// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is &lt; 0.</exception>
		public new void CopyTo(int index, object[] array, int arrayIndex, int count)
		{
			if (count >= this.Count - index)
				throw new ArgumentOutOfRangeException("count", count, "So many items are not available.");
			base.CopyTo(TranslateIndex(index, true), array, arrayIndex, count);
		}

		/// <summary>
		/// Whether <paramref name="index"/> is valid outer index.
		/// </summary>
		/// <param name="index">Index to be validated.</param>
		/// <param name="countIsOK">Whether <see cref="Count"/> is valid <paramref name="index"/> value.</param>
		/// <returns><paramref name="index"/> validity.</returns>
		protected bool IsValidIndex(int index, bool countIsOK)
		{
			if (countIsOK)
				return index >= 0 && index <= this.Count;
			else
				return index >= 0 && index < this.Count;
		}
		/// <summary>
		/// Calls <see cref="IsValidIndex"/>.
		/// </summary>
		/// <param name="index">Index to be validated.</param>
		/// <param name="countIsOK">Whether <see cref="Count"/> is valid <paramref name="index"/> value.</param>
		/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid.</exception>
		protected void ValidateIndex(int index, bool countIsOK)
		{
			if (IsValidIndex(index, countIsOK))
				return;
			if (countIsOK)
				throw new ArgumentOutOfRangeException("index", index, "Must be in range <0, Count>.");
			else
				throw new ArgumentOutOfRangeException("index", index, "Must be in range <0, Count).");
		}

		#region Events

		/// <summary>
		/// Translates full list changes (indexes to subset indexes etc.).
		/// Resizes subset if needed to accomodate to changes.
		/// </summary>
		protected override void OnItemsChange(ListItemsChangeEventArgs<object> e)
		{
			if (!this.AllowListItemsChange)
				return;
			ListItemsChangeEventArgs<object> args = null;
			int newIndex, oldIndex;
			switch (e.Change)
			{
				case ListItemsChanges.Add:
					newIndex = TranslateIndex(e.NewIndex, false);
					if (IsValidIndex(newIndex, true))
					{
						using (SuppressListItemsChange())
							PrepareSubsetOnItemAdded(e.NewItem, newIndex);
						args = new ListItemsChangeEventArgs<object>(ListItemsChanges.Add, e.NewItem, newIndex);
					}
					else
					{
						// subset was shifted downward by GetItemTCount
						if (e.NewIndex < this.FlatStartIndex)
							goto case ListItemsChanges.Reset;
						else
							return;
					}
					break;
				case ListItemsChanges.Remove:
					oldIndex = TranslateIndex(e.OldIndex, false);
					if (IsValidIndex(oldIndex, false))
					{
						using (SuppressListItemsChange())
							PrepareSubsetOnItemRemoved(e.OldItem, oldIndex);
						args = new ListItemsChangeEventArgs<object>(ListItemsChanges.Remove, e.OldItem, oldIndex);
					}
					else
					{
						// subset was shifted upward by GetItemTCount
						if (e.OldIndex < this.FlatStartIndex)
							goto case ListItemsChanges.Reset;
						else
							return;
					}
					break;
				case ListItemsChanges.Replace:
					newIndex = TranslateIndex(e.NewIndex, false);
					if (IsValidIndex(newIndex, false))
					{
						using (SuppressListItemsChange())
							PrepareSubsetOnItemReplaced(e.NewIndex, e.OldItem, newIndex);
						args = new ListItemsChangeEventArgs<object>(ListItemsChanges.Replace, e.NewItem, e.OldItem, newIndex);
					}
					else
					{
						// subset was shifted upward by GetItemTCount(newItem) - GetItemTCount(oldItem)
						if (e.NewIndex < this.FlatStartIndex)
							goto case ListItemsChanges.Reset;
						else
							return;
					}
					break;
				case ListItemsChanges.Move:
					newIndex = TranslateIndex(e.NewIndex, false);
					oldIndex = TranslateIndex(e.OldIndex, false);
					bool newIndexIsValid = IsValidIndex(newIndex, false);
					bool oldIndexIsValid = IsValidIndex(oldIndex, false);
					if (newIndexIsValid && oldIndexIsValid)
					{
						int lastIndex = this.Count - 1;
						// if first item is moved and it is not full subset list, go to Reset
						if (oldIndex == 0 || newIndex == 0)
						{
							ISubsetListBase<T> firstItemSubsetList = this[0] as ISubsetListBase<T>;
							if (firstItemSubsetList != null && firstItemSubsetList.TStartIndex != 0)
								goto case ListItemsChanges.Reset;
						}
						// if last item is moved and it is not full subset list, go to Reset
						else if (oldIndex == lastIndex || newIndex == lastIndex)
						{
							ISubsetListBase<T> lastItemSubsetList = this[lastIndex] as ISubsetListBase<T>;
							if (lastItemSubsetList != null && lastItemSubsetList.TEndIndex != lastItemSubsetList.TCount - 1)
								goto case ListItemsChanges.Reset;
						}
						// just normal (full) item move without changing item subsets
						else
						{
							args = new ListItemsChangeEventArgs<object>(ListItemsChanges.Move, e.NewItem, newIndex, oldIndex);
						}
					}
					else if (newIndexIsValid)
					{
						if (e.OldIndex < this.FlatStartIndex)
							goto case ListItemsChanges.Reset;
						else
							goto case ListItemsChanges.Add;
					}
					else if (oldIndexIsValid)
					{
						if (e.NewIndex < this.FlatStartIndex)
							goto case ListItemsChanges.Reset;
						else
							goto case ListItemsChanges.Remove;
					}
					else
						return;
					break;
				case ListItemsChanges.Reset:
					args = new ListItemsChangeEventArgs<object>(ListItemsChanges.Reset);
					using (SuppressListItemsChange())
						PrepareSubset();
					break;
			}
			base.OnItemsChange(args);
		}

		/// <summary>
		/// Resizes subset if needed to accomodate to changes when item was added.
		/// Called from <see cref="OnItemsChange"/>.
		/// </summary>
		/// <param name="item">Item.</param>
		/// <param name="index">Outer index.</param>
		private void PrepareSubsetOnItemAdded(object item, int index)
		{
			ResetItemSubsetList(item);
			int increment = GetItemTCount(item);
			int count = this.Count;
			if (count > 1)
			{
				// item was added at the beginning of the subset
				if (index == 0)
				{
					ISubsetListBase<T> nextItemSubsetList = this[1] as ISubsetListBase<T>;
					// reset TStartIndex of next item to 0 and shift this.TStartIndex downward
					if (nextItemSubsetList != null)
					{
						int shift = nextItemSubsetList.TStartIndex;
						nextItemSubsetList.TStartIndex = 0;
						IncrementStartIndex(-shift);
					}
				}
				// item was added at the end of the subset
				else if (index == count - 1)
				{
					ISubsetListBase<T> previousItemSubsetList = this[count - 2] as ISubsetListBase<T>;
					// reset TEndIndex of previous item to int.MaxValue and shift this.TEndIndex upward
					if (previousItemSubsetList != null)
					{
						int shift = previousItemSubsetList.TCount - 1 - previousItemSubsetList.TEndIndex;
						previousItemSubsetList.TEndIndex = int.MaxValue;
						increment += shift;
					}
				}
			}
			IncrementEndIndex(increment);
		}
		/// <summary>
		/// Resizes subset if needed to accomodate to changes when item was removed.
		/// Called from <see cref="OnItemsChange"/>.
		/// </summary>
		/// <param name="item">Item.</param>
		/// <param name="index">Outer index.</param>
		private void PrepareSubsetOnItemRemoved(object item, int index)
		{
			int increment = GetItemTCount(item);
			int count = this.Count;
			ISubsetListBase<T> itemSubsetList = item as ISubsetListBase<T>;
			if (itemSubsetList != null)
			{
				// item was removed from the beginning of the subset
				// shift this.TStartIndex downward by itemSubsetList.TStartIndex
				if (index == 0)
				{
					int shift = itemSubsetList.TStartIndex;
					int tEndIndex = _tEndIndex;
					// can change TEndIndex (TStartIndex calls EnsureSubsetRange),
					// because item was removed (TCount was changed), which is not desired now
					IncrementStartIndex(-shift);
					_tEndIndex = tEndIndex;
				}
				// item was removed from the end of the subset
				// shift this.TEndIndex upward
				if (index == count)
				{
					int shift = itemSubsetList.TCount - 1 - itemSubsetList.TEndIndex;
					increment += shift;
				}
			}
			IncrementEndIndex(-increment);
		}
		/// <summary>
		/// Resizes subset if needed to accomodate to changes when item was replaced.
		/// Called from <see cref="OnItemsChange"/>.
		/// </summary>
		/// <param name="newItem">New item.</param>
		/// <param name="oldItem">Old item.</param>
		/// <param name="index">Outer index.</param>
		private void PrepareSubsetOnItemReplaced(object newItem, object oldItem, int index)
		{
			PrepareSubsetOnItemRemoved(oldItem, index);
			PrepareSubsetOnItemAdded(newItem, index);
		}

		#endregion

		#region IHierarchicalSubsetList<T> Members

		/// <summary>
		/// Full list.
		/// </summary>
		public IList<object> List
		{
			get { return this as TwoTypesIndexBasedList<TSubsetList, T>; }
		}

		/// <summary>
		/// See <see cref="IHierarchicalSubsetList{T}.IsItemSubsetList"/>.
		/// </summary>
		public bool IsItemSubsetList(int index)
		{
			return IsItemSubsetList(index, false);
		}
		/// <summary>
		/// See <see cref="IHierarchicalSubsetList{T}.GetItemSubsetList"/>.
		/// </summary>
		public ISubsetListBase<T> GetItemSubsetList(int index)
		{
			return GetItemSubsetList(index, false);
		}
		/// <summary>
		/// See <see cref="IHierarchicalSubsetList{T}.GetItemTCount"/>.
		/// </summary>
		public int GetItemTCount(int index)
		{
			return GetItemTCount(index, false);
		}
		/// <summary>
		/// See <see cref="IHierarchicalSubsetList{T}.GetItemTStartIndex"/>.
		/// </summary>
		public int GetItemTStartIndex(int index)
		{
			ValidateIndex(index, false);
			int tStartIndex = 0;
			int innerIndex;
			int itemTCount = 0;
			int globalIndex = TranslateIndex(index, true);
			for (int i = 0; i <= globalIndex; i++)
			{
				innerIndex = GetInnerIndex(i);
				itemTCount = GetItemTCount(innerIndex, true);
				tStartIndex += itemTCount;
			}
			tStartIndex -= itemTCount;
			if (itemTCount > 0)
				tStartIndex++;
			return tStartIndex;
		}
		/// <summary>
		/// See <see cref="IHierarchicalSubsetList{T}.GetItemTEndIndex"/>.
		/// </summary>
		public int GetItemTEndIndex(int index)
		{
			return GetItemTStartIndex(index) + Math.Max(0, GetItemTCount(index) - 1);
		}

		/// <summary>
		/// Whether item at <paramref name="index"/> implements <see cref="ISubsetListBase{T}"/>.
		/// Called by <see cref="IsItemSubsetList(int)"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="isInnerIndex">
		/// If true <paramref name="index"/> is inner index, otherwise it is outer index.
		/// </param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="isInnerIndex"/> is false and <paramref name="index"/> is invalid.
		/// </exception>
		protected bool IsItemSubsetList(int index, bool isInnerIndex)
		{
			if (isInnerIndex)
				return index >= 0;
			else
				return GetInnerIndex(TranslateIndex(index, true)) >= 0;
		}
		/// <summary>
		/// Gets item implementing <see cref="ISubsetListBase{T}"/> at <paramref name="index"/>.
		/// Called by <see cref="GetItemSubsetList(int)"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="isInnerIndex">
		/// If true <paramref name="index"/> is inner index, otherwise it is outer index.
		/// </param>
		/// <returns>Subset list if the item implements <see cref="ISubsetListBase{T}"/>, otherwise null.</returns>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="isInnerIndex"/> is false and <paramref name="index"/> is invalid.
		/// </exception>
		protected ISubsetListBase<T> GetItemSubsetList(int index, bool isInnerIndex)
		{
			int innerIndex = isInnerIndex ? index : GetInnerIndex(TranslateIndex(index, true));
			if (IsItemSubsetList(innerIndex, true))
				return GetItem(innerIndex) as ISubsetListBase<T>;
			else
				return null;
		}
		/// <summary>
		/// Number of <typeparamref name="T"/> items in item at <paramref name="index"/>.
		/// Called by <see cref="GetItemTCount(int)"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="isInnerIndex">
		/// If true <paramref name="index"/> is inner index, otherwise it is outer index.
		/// </param>
		/// <returns>
		/// If <see cref="IsItemSubsetList(int, bool)"/> is true, returns <see cref="ISubsetListBase{T}.TCount"/> of subset list from <see cref="GetItemSubsetList(int, bool)"/>, otherwise 1.
		/// </returns>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="isInnerIndex"/> is false and <paramref name="index"/> is invalid.
		/// </exception>
		protected int GetItemTCount(int index, bool isInnerIndex)
		{
			if (IsItemSubsetList(index, isInnerIndex))
				return GetItemSubsetList(index, isInnerIndex).TCount;
			else
				return 1;
		}
		/// <summary>
		/// Gets number of <typeparamref name="T"/> items in <paramref name="item"/>
		/// </summary>
		/// <remarks>Calls <see cref="ValidateItemType"/>.</remarks>
		/// <param name="item">Item.</param>
		/// <returns>Increment.</returns>
		protected int GetItemTCount(object item)
		{
			ValidateItemType(item);
			ISubsetListBase<T> subsetList = item as ISubsetListBase<T>;
			if (subsetList != null)
				return subsetList.TCount;
			else
				return 1;
		}
		/// <summary>
		/// Calls <paramref name="item"/>.<see cref="ISubsetListBase{T}.ResetSubset"/> if <paramref name="item"/> is <see cref="ISubsetListBase{T}"/>.
		/// </summary>
		/// <param name="item">Item.</param>
		protected void ResetItemSubsetList(object item)
		{
			ISubsetListBase<T> itemSubsetList = item as ISubsetListBase<T>;
			if (itemSubsetList != null)
				itemSubsetList.ResetSubset();
		}
		/// <summary>
		/// Calls item`s <see cref="ISubsetListBase{T}.ResetSubset"/> if item given by <paramref name="index"/> is <see cref="ISubsetListBase{T}"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="isInnerIndex">
		/// If true <paramref name="index"/> is inner index, otherwise it is outer index.
		/// </param>
		/// <returns>Subset list from <see cref="GetItemSubsetList(int, bool)"/>.</returns>
		protected ISubsetListBase<T> ResetItemSubsetList(int index, bool isInnerIndex)
		{
			ISubsetListBase<T> itemSubsetList = GetItemSubsetList(index, isInnerIndex);
			if (itemSubsetList != null)
				itemSubsetList.ResetSubset();
			return itemSubsetList;
		}

		#endregion

		#region ISubsetListBase<T> Members

		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TStartIndex"/>.
		/// </summary>
		public int TStartIndex
		{
			get
			{
				EnsureSubsetRange(-1);
				return _tStartIndex;
			}
			set
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				SetSubsetIndex(value, _tEndIndex, tCount);
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TEndIndex"/>.
		/// </summary>
		public int TEndIndex
		{
			get
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				return GetEndIndex(_tEndIndex, tCount);
			}
			set
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				SetSubsetIndex(_tStartIndex, value, tCount);
			}
		}
		/// <summary>
		/// Starting index of subset relative to full flat list.
		/// </summary>
		/// <remarks>Calls <see cref="PrepareSubset"/>.</remarks>
		/// <value>Default: 0.</value>
		public int FlatStartIndex
		{
			get
			{
				PrepareSubset();
				return _flatStartIndex;
			}
		}
		/// <summary>
		/// Ending index of subset relative to full flat list.
		/// </summary>
		/// <remarks>Calls <see cref="PrepareSubset"/>.</remarks>
		/// <value><see cref="FlatEndIndexInternal"/>.</value>
		public int FlatEndIndex
		{
			get
			{
				PrepareSubset();
				return this.FlatEndIndexInternal;
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SubsetCount"/>.
		/// </summary>
		public int SubsetCount
		{
			get
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				return GetEndIndex(_tEndIndex, tCount) - _tStartIndex + 1;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("SubsetCount", value, "Must be >= 0.");
				if (value == int.MaxValue)
					this.TEndIndex = value;
				else
					this.TEndIndex = _tStartIndex + value - 1;
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TCount"/>.
		/// </summary>
		/// <value>
		/// Sum of <see cref="ISubsetListBase{T}.TCount"/>s of all <see cref="List1"/> items + number of items in <see cref="List2"/>.
		/// </value>
		public int TCount
		{
			get
			{
				// TODO: Optimize it?
				int tCount = 0;
				int innerIndex;
				int count = base.Count;
				for (int i = 0; i < count; i++)
				{
					innerIndex = GetInnerIndex(i);
					tCount += GetItemTCount(innerIndex, true);
				}
				return tCount;
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.ResetSubset"/>.
		/// </summary>
		public void ResetSubset()
		{
			SetSubsetIndex(0, int.MaxValue);
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SetSubsetIndex"/>.
		/// </summary>
		public void SetSubsetIndex(int startIndex, int endIndex)
		{
			SetSubsetIndex(startIndex, endIndex, -1);
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SetSubsetCount"/>.
		/// </summary>
		public void SetSubsetCount(int startIndex, int subsetCount)
		{
			SetSubsetIndex(startIndex, startIndex + subsetCount - 1);
		}

		/// <summary>
		/// Translates <paramref name="index"/> between subset (<see cref="IList{}"/>) and full list.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="fromSubset">
		/// If true, translates <paramref name="index"/> from subset to full list.
		/// If false, translates <paramref name="index"/> from full list to subset.
		/// </param>
		/// <returns>Translated index.</returns>
		protected int TranslateIndex(int index, bool fromSubset)
		{
			if (fromSubset)
				return index + this.FlatStartIndex;
			else
				return index - this.FlatStartIndex;
		}
		/// <summary>
		/// Repairs invalid <see cref="TStartIndex"/> and <see cref="TEndIndex"/> (calls <see cref="ResetSubset"/>) if List was externally modified.
		/// </summary>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		protected void EnsureSubsetRange(int tCount)
		{
			if (tCount < 0)
				tCount = this.TCount;
			if (
				!IsValidTStartIndex(_tStartIndex, _tEndIndex, tCount) ||
				!IsValidTEndIndex(_tStartIndex, _tEndIndex, tCount))
			{
				ResetSubset();
			}
		}
		/// <summary>
		/// Whether <paramref name="tStartIndex"/> is valid.
		/// </summary>
		/// <param name="tStartIndex">Start index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tEndIndex">End index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		protected bool IsValidTStartIndex(int tStartIndex, int tEndIndex, int tCount)
		{
			if (tStartIndex < 0)
				return false;
			if (tCount < 0)
				tCount = this.TCount;
			if (tCount == 0)
				return tStartIndex == 0;
			// tCount > 0
			else
				return tStartIndex < tCount && (tEndIndex == int.MaxValue || tStartIndex <= tEndIndex + 1);
		}
		/// <summary>
		/// Whether <paramref name="tEndIndex"/> is valid.
		/// </summary>
		/// <param name="tStartIndex">Start index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tEndIndex">End index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		protected bool IsValidTEndIndex(int tStartIndex, int tEndIndex, int tCount)
		{
			if (tEndIndex == int.MaxValue)
				return true;
			if (tCount < 0)
				tCount = this.TCount;
			return tEndIndex < tCount && (tStartIndex <= tEndIndex + 1);
		}
		/// <summary>
		/// Prepares <see cref="FlatStartIndex"/> and <see cref="FlatEndIndex"/> which limit subset.
		/// </summary>
		protected void PrepareSubset()
		{
			// TODO: Optimize it to be called only when necessary.
			if (_preparingSubset)
				return;
			_preparingSubset = true;
			int startIndex = this.TStartIndex;
			int endIndex = this.TEndIndex;
			int flatStartIndex = 0;
			int flatEndIndex = int.MaxValue;
			if (startIndex > endIndex)
			{
				flatEndIndex = -1;
			}
			else
			{
				bool flatStartIndexIsSet = false;
				bool flatEndIndexIsSet = false;
				int innerIndex;
				ISubsetListBase<T> subsetList;
				using (SuppressListItemsChange())
				{
					if (startIndex == 0)
					{
						flatStartIndexIsSet = true;
						//flatStartIndex = 0;
						// reset previous first item
						innerIndex = GetInnerIndex(_flatStartIndex);
						ResetItemSubsetList(innerIndex, true);
					}
					if (_tEndIndex == int.MaxValue)
					{
						flatEndIndexIsSet = true;
						//flatEndIndex = int.MaxValue;
						// reset previous last item
						if (this.FlatEndIndexInternal >= 0)
						{
							innerIndex = GetInnerIndex(this.FlatEndIndexInternal);
							ResetItemSubsetList(innerIndex, true);
						}
					}
					if (!flatStartIndexIsSet || !flatEndIndexIsSet)
					{
						int count = base.Count;
						int tSubcount = 0;
						int subsetListTCount = 0;
						for (int i = 0; i < count; i++)
						{
							innerIndex = GetInnerIndex(i);
							subsetList = ResetItemSubsetList(innerIndex, true);
							subsetListTCount = GetItemTCount(innerIndex, true);
							tSubcount += subsetListTCount;
							if (!flatStartIndexIsSet && tSubcount > startIndex)
							{
								flatStartIndexIsSet = true;
								flatStartIndex = i;
								if (subsetList != null)
								{
									int subsetListFirstTItemIndex = tSubcount - subsetListTCount;
									// if startIndex is not at the start of item`s subset list, we trim its items at the beginning
									if (subsetListFirstTItemIndex < startIndex)
										subsetList.TStartIndex = startIndex - subsetListFirstTItemIndex;
								}
							}
							if (!flatEndIndexIsSet && tSubcount > endIndex)
							{
								flatEndIndexIsSet = true;
								flatEndIndex = i;
								if (subsetList != null)
								{
									int subsetListLastTItemIndex = tSubcount - 1;
									// if endIndex is smaller than the end of item`s subset list, we trim its items at the end
									if (subsetListLastTItemIndex > endIndex)
										subsetList.TEndIndex = subsetListTCount - 1 - (subsetListLastTItemIndex - endIndex);
									// if endIndex is at the end of item`s subset list, add next empty items, if found
									else if (subsetListLastTItemIndex == endIndex)
									{
										for (int j = i + 1; j < count; j++)
										{
											innerIndex = GetInnerIndex(j);
											if (GetItemTCount(innerIndex, true) == 0)
												flatEndIndex++;
											else
												break;
										}
									}
								}
							}
							if (flatStartIndexIsSet && flatEndIndexIsSet)
								break;
						}
					}
				}
			}
			if (_flatStartIndex == flatStartIndex && _flatEndIndex == flatEndIndex)
			{
				_preparingSubset = false;
				return;
			}
			_flatStartIndex = flatStartIndex;
			_flatEndIndex = flatEndIndex;
			OnItemsReset();
			_preparingSubset = false;
		}
		/// <summary>
		/// Almost same as <see cref="ISubsetListBase{T}.SetSubsetIndex"/>, but can accept <paramref name="tCount"/> parameter value for optimization.
		/// </summary>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="startIndex"/> or <paramref name="endIndex"/> is invalid.
		/// </exception>
		protected void SetSubsetIndex(int startIndex, int endIndex, int tCount)
		{
			if (_tStartIndex == startIndex && _tEndIndex == endIndex)
				return;
			if (tCount < 0)
				tCount = this.TCount;
			if (!IsValidTStartIndex(startIndex, endIndex, tCount))
			{
				throw new ArgumentOutOfRangeException("startIndex", startIndex,
					"Must be in range <0, Min(TCount - 1, endIndex + 1)>.");
			}
			if (!IsValidTEndIndex(startIndex, endIndex, tCount))
			{
				throw new ArgumentOutOfRangeException("endIndex", endIndex,
					"Must be in range <startIndex - 1, TCount) or int.MaxValue.");
			}
			_tStartIndex = startIndex;
			_tEndIndex = endIndex;
			OnItemsReset();
		}

		/// <summary>
		/// Ending index of subset relative to full flat list.
		/// </summary>
		/// <remarks>Calls <see cref="PrepareSubset"/>.</remarks>
		/// <value>
		/// If <see cref="_flatEndIndex"/> is set to <see cref="int.MaxValue"/>, it returns <see cref="Count"/> - 1, otherwise unchanged <see cref="_flatEndIndex"/>.
		/// Default: <see cref="int.MaxValue"/>.
		/// </value>
		private int FlatEndIndexInternal
		{
			get
			{
				if (_flatEndIndex == int.MaxValue)
					return base.Count - 1;
				else
					return _flatEndIndex;
			}
		}

		/// <summary>
		/// Gets end index of this subset (almost same as <see cref="TEndIndex"/> but without calling <see cref="EnsureSubsetRange"/>).
		/// </summary>
		/// <param name="endIndex">Raw end index.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		/// <returns>
		/// If <paramref name="endIndex"/> is <see cref="int.MaxValue"/>, returns (TCount or tCount) - 1, otherwise <paramref name="endIndex"/>.
		/// </returns>
		private int GetEndIndex(int endIndex, int tCount)
		{
			if (tCount < 0)
				tCount = this.TCount;
			if (endIndex == int.MaxValue)
				return tCount - 1;
			else
				return endIndex;
		}
		/// <summary>
		/// Increments (decrements) <see cref="TStartIndex"/> by given <paramref name="increment"/>.
		/// </summary>
		/// <remarks>
		/// Does nothing if <paramref name="increment"/> is 0.
		/// Handles <see cref="OverflowException"/> if it occures.
		/// </remarks>
		/// <param name="increment">Increment to be added to <see cref="TStartIndex"/>.</param>
		private void IncrementStartIndex(int increment)
		{
			if (increment == 0)
				return;
			int startIndex;
			try
			{
				checked
				{
					startIndex = _tStartIndex + increment;
				}
			}
			catch (OverflowException)
			{
				if (increment > 0)
					startIndex = _tEndIndex + 1;
				else
					startIndex = 0;	//this will never happen ;-)
			}
			if (startIndex < 0)
				startIndex = 0;
			this.TStartIndex = startIndex;
		}
		/// <summary>
		/// Increments (decrements) <see cref="TEndIndex"/> by given <paramref name="increment"/>.
		/// </summary>
		/// <remarks>
		/// Does nothing if <see cref="TEndIndex"/> was set with <see cref="int.MaxValue"/> or <paramref name="increment"/> is 0.
		/// Handles <see cref="OverflowException"/> if it occures.
		/// </remarks>
		/// <param name="increment">Increment to be added to <see cref="TEndIndex"/>.</param>
		private void IncrementEndIndex(int increment)
		{
			if (increment == 0 || _tEndIndex == int.MaxValue)
				return;
			int endIndex;
			try
			{
				checked
				{
					endIndex = _tEndIndex + increment;
				}
			}
			catch (OverflowException)
			{
				if (increment > 0)
					endIndex = int.MaxValue;
				else
					endIndex = _tStartIndex - 1;	//this will never happen ;-)
			}
			if (endIndex < _tStartIndex - 1)
				endIndex = _tStartIndex - 1;
			this.TEndIndex = endIndex;
		}

		/// <summary>
		/// See <see cref="TStartIndex"/>.
		/// </summary>
		private int _tStartIndex;
		/// <summary>
		/// See <see cref="TEndIndex"/>.
		/// </summary>
		private int _tEndIndex = int.MaxValue;
		/// <summary>
		/// See <see cref="FlatStartIndex"/>.
		/// </summary>
		private int _flatStartIndex = 0;
		/// <summary>
		/// See <see cref="FlatEndIndex"/>.
		/// </summary>
		private int _flatEndIndex = int.MaxValue;
		/// <summary>
		/// Whether <see cref="PrepareSubset"/> is called now.
		/// </summary>
		private bool _preparingSubset;

		#endregion

		#region IList<object> Members

		/// <summary>
		/// See <see cref="IList{object}.IndexOf"/>.
		/// </summary>
		public new int IndexOf(object item)
		{
			int index = base.IndexOf(item);
			if (index >= 0)
			{
				if (index < this.FlatStartIndex || index > this.FlatEndIndexInternal)
					index = -1;
				else
					index = TranslateIndex(index, false);
			}
			return index;
		}
		/// <summary>
		/// See <see cref="IList{object}.Insert"/>.
		/// </summary>
		public new void Insert(int index, object item)
		{
			ValidateIndex(index, true);
			int listIndex = TranslateIndex(index, true);
			base.Insert(listIndex, item);
		}
		/// <summary>
		/// See <see cref="IList{object}.RemoveAt"/>.
		/// </summary>
		public new void RemoveAt(int index)
		{
			ValidateIndex(index, false);
			object item = this[index];
			int listIndex = TranslateIndex(index, true);
			base.RemoveAt(listIndex);
		}
		/// <summary>
		/// See <see cref="IList{object}.Item[]"/>.
		/// </summary>
		public new object this[int index]
		{
			get
			{
				ValidateIndex(index, false);
				return base[TranslateIndex(index, true)];
			}
			set
			{
				ValidateIndex(index, false);
				object oldItem = this[index];
				int listIndex = TranslateIndex(index, true);
				base[listIndex] = value;
			}
		}

		#endregion

		#region ICollection<object> Members

		/// <summary>
		/// See <see cref="ICollection{object}.Add"/>.
		/// </summary>
		public new void Add(object item)
		{
			int index;
			if (this.IsUnited)
			{
				index = GetLastUnitedIndexInSubstore(item) + 1;
				index = TranslateIndex(index, false);
			}
			else
			{
				index = this.Count;
			}
			Insert(index, item);
		}
		/// <summary>
		/// See <see cref="ICollection{object}.Clear"/>.
		/// </summary>
		public new void Clear()
		{
			using (SuppressListItemsChange())
			{
				int startIndex = 0;
				if (this.SubsetCount == this.TCount)
				{
					base.Clear();
				}
				else
				{
					startIndex = this.FlatStartIndex;
					for (int i = this.FlatEndIndexInternal; i >= startIndex; i--)
						base.RemoveAt(i);
				}
				startIndex = this.FlatStartIndex;
				SetSubsetIndex(startIndex, _tEndIndex == int.MaxValue ? int.MaxValue : startIndex - 1);
			}
			OnItemsReset();
		}
		/// <summary>
		/// See <see cref="ICollection{object}.Contains"/>.
		/// </summary>
		public new bool Contains(object item)
		{
			return IndexOf(item) >= 0;
		}
		/// <summary>
		/// See <see cref="ICollection{object}.CopyTo"/>.
		/// </summary>
		public new void CopyTo(object[] array, int arrayIndex)
		{
			CopyTo(0, array, arrayIndex, this.Count);
		}
		/// <summary>
		/// See <see cref="ICollection{object}.Count"/>.
		/// </summary>
		/// <value><see cref="FlatEndIndex"/> - <see cref="FlatStartIndex"/> + 1.</value>
		public new int Count
		{
			get { return -this.FlatStartIndex + this.FlatEndIndexInternal + 1; }
		}
		/// <summary>
		/// See <see cref="ICollection{object}.Remove"/>.
		/// </summary>
		public new bool Remove(object item)
		{
			int index = IndexOf(item);
			if (index < 0)
				return false;
			try
			{
				RemoveAt(index);
			}
			catch (Exception)
			{
				return false;
			}
			return true;
		}

		#endregion

		#region IEnumerable<object> Members

		/// <summary>
		/// See <see cref="IEnumerable{object}.GetEnumerator"/>.
		/// </summary>
		public new IEnumerator<object> GetEnumerator()
		{
			int startIndex = this.FlatStartIndex;
			int endIndex = this.FlatEndIndexInternal;
			for (int i = startIndex; i <= endIndex; i++)
			{
				yield return base[i];
			}
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// See <see cref="IEnumerable.GetEnumerator"/>.
		/// </summary>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}
}
